//
// (C) Copyright 2009 Irantha Suwandarathna (irantha@gmail.com)
// All rights reserved.
//

/* Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.Reflection;
using System.IO;
using EffiProzDB.Lib;
using EffiProzDB.ScriptIO;
using EffiProzDB.Persist;



namespace EffiProzDB
{

    // tony_lai@users 20020820 - patch 595099 - user define PK name

    /**
     * Subclass of Table to handle TEXT data source. <p>
     *
     *:Table to provide the notion of an SQL base table object whose
     * data is read from and written to a text format data file.
     *
     * @author sqlbob@users (RMP)
     * @version    1.8.0
     */
    public class TextTable : Table
    {

        private string _dataSource = "";
        private bool _isReversed = false;
        private bool _isConnected = false;

        /**
         *  Constructs a new TextTable from the given arguments.
         *
         * @param  db the owning database
         * @param  name the table's HsqlName
         * @param  type (normal or temp text table)
         * @param  sessionid the id of the owning session (for temp table)
         * @exception  HsqlException  Description of the Exception
         */
        public TextTable(Database db, HsqlNameManager.HsqlName name,
                  int type) :base(db, name, type)
        {
            
        }

        /**
         * common handling for all errors during <code>connect</code>
         */
        private void onConnectError(Session session)
        {

            if (cache != null)
            {
                try
                {
                    cache.close(false);
                }
                catch (HsqlException ) { }
            }

            cache = null;

            clearAllRows(session);
        }

        public override bool isConnected()
        {
            return _isConnected;
        }

        /**
         * connects to the data source
         */
        public override void connect(Session session)
        {
            connect(session, _isReadOnly);
        }

        /**
         * connects to the data source
         */
        public void connect(Session session,
                             bool withReadOnlyData)
        {

            // Open new cache:
            if ((_dataSource.Length == 0) || _isConnected)
            {

                // nothing to do
                return;
            }

            try
            {
                cache = database.logger.openTextCache(this, _dataSource,
                                                      withReadOnlyData,
                                                      _isReversed);

                // read and insert all the rows from the source file
                CachedRow row = null;
                int nextpos = 0;

                if (((TextCache)cache).ignoreFirst)
                {
                    nextpos += ((TextCache)cache).readHeaderLine();
                }

                while (true)
                {
                    row = (CachedRow)rowStore.get(nextpos);

                    if (row == null)
                    {
                        break;
                    }

                    nextpos = row.getPos() + row.getStorageSize();

                    row.setNewNodes();
                    insertFromTextSource(row);
                }
            }
            catch (HsqlException e)
            {
                int linenumber = cache == null ? 0
                                               : ((TextCache)cache)
                                                   .getLineNumber();

                onConnectError(session);

                // everything is _in order here.
                // At this point table should either have a valid (old) data
                // source and cache or have an empty source and null cache.
                throw Trace.error(Trace.TEXT_FILE, new Object[] {
                  (linenumber), e.Message 
            });
            }
            catch (Exception t)
            {
                int linenumber = cache == null ? 0
                                               : ((TextCache)cache)
                                                   .getLineNumber();

                onConnectError(session);

                throw Trace.error(Trace.TEXT_FILE, new Object[] {
                  (linenumber),
                t.GetType().Name + ": " + t.Message 
            });
            }

            _isConnected = true;

            setIsReadOnly(withReadOnlyData);
        }

        /**
         * disconnects from the data source
         */
        public override void disconnect(Session session)
        {

            // Close old cache:
            database.logger.closeTextCache(this);

            cache = null;

            clearAllRows(session);

            _isConnected = false;
        }

        /**
         * This method does some of the work involved with managing the creation
         * and openning of the cache, the rest is done _in Log.java and
         * TextCache.java.
         *
         * Better clarification of the role of the methods is needed.
         */
        private void openCache(Session session, string dataSourceNew,
                               bool isReversedNew,
                               bool isReadOnlyNew)
        {

            if (dataSourceNew == null)
            {
                dataSourceNew = "";
            }

            disconnect(session);

            _dataSource = dataSourceNew;
            _isReversed = (isReversedNew && _dataSource.Length > 0);

            connect(session, isReadOnlyNew);
        }

        /**
         * High level command to assign a data source to the table definition.
         * Reassigns only if the data source or direction has changed.
         */
        public override void setDataSource(Session session, string dataSourceNew,
                                     bool isReversedNew,
                                     bool newFile)
        {

            if (getTableType() == Table.TEMP_TEXT_TABLE)
            {
                ;
            }
            else
            {
                session.checkAdmin();
            }

            dataSourceNew = dataSourceNew.Trim();

            if (newFile && FileUtil.GetDefaultInstance().exists(dataSourceNew))
            {
                throw Trace.error(Trace.TEXT_SOURCE_EXISTS, dataSourceNew);
            }

            //-- Open if descending, direction changed, file changed, or not connected currently
            if (isReversedNew || (isReversedNew != _isReversed)
                    || !_dataSource.Equals(dataSourceNew) || !_isConnected)
            {
                openCache(session, dataSourceNew, isReversedNew, _isReadOnly);
            }

            if (_isReversed)
            {
                setIsReadOnly(true);
            }
        }

        public override string getDataSource()
        {
            return _dataSource;
        }

        public override bool isDescDataSource()
        {
            return _isReversed;
        }

        public override void setHeader(string header)
        {

            if (cache != null && ((TextCache)cache).ignoreFirst)
            {
                ((TextCache)cache).setHeader(header);

                return;
            }

            throw Trace.error(Trace.TEXT_TABLE_HEADER);
        }

        public override string getHeader()
        {

            string header = cache == null ? null
                                          : ((TextCache)cache).getHeader();

            return header == null ? null
                                  : StringConverter.toQuotedString(header, '\"',
                                  true);
        }

        /**
         * Used by INSERT, DELETE, UPDATE operations. This class will return
         * a more appropriate message when there is no data source.
         */
        public override void checkDataReadOnly()
        {

            if (_dataSource.Length == 0)
            {
                throw Trace.error(Trace.UNKNOWN_DATA_SOURCE);
            }

            if (isDataReadOnly())
            {
                throw Trace.error(Trace.DATA_IS_READONLY);
            }
        }

        public override bool isDataReadOnly()
        {
            return !isConnected() || base.isDataReadOnly();
        }

        public override void setDataReadOnly(bool value)
        {

            if (_isReversed && value == false)
            {
                throw Trace.error(Trace.DATA_IS_READONLY);
            }

            openCache(null, _dataSource, _isReversed, value);
            setIsReadOnly(value);
        }

        public override bool isIndexCached()
        {
            return false;
        }

        protected override Table duplicate()
        {
            return new TextTable(database, tableName, getTableType());
        }

        public override void drop()
        {
            openCache(null, "", false, false);
        }

        public override void setIndexRoots(string s)
        {

            // do nothing
        }
    }
}
